/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

// loading
#include "quakedef.h"

// let's be honest - I hate LordHavoc's cvar names.
// r_shadows_lighting_shadowing_exoticoptionA_exoticoptionB_someweirdsettingthatonlyapplieswhenthemoonisfullanditsfridayandifeellikeit ???
// no thanks.
cvar_t *r_shadows;
cvar_t *r_shadowmapdepthbias = NULL;

// matches qtools entities.c
#define DEFAULTLIGHTLEVEL	300

dlight_t *r_activelights[MAX_ACTIVE_LIGHTS];
dlight_t r_realtimelights[MAX_MAP_ENTITIES];
int r_num_realtimelights = 0;
int r_numactivelights = 0;

QTEXTURE *R_GetShadowBuffer (dlight_t *dl);


void R_DefaultRTLight (dlight_t *light)
{
	Vector3Set (light->angles, 0, 0, 0);
	Vector3Set (light->color, 1, 1, 1);

	light->ambientscale = 0;
	light->baseradius = light->radius = DEFAULTLIGHTLEVEL;
	light->diffusescale = 1;
	light->maxdist = light->radius * light->radius;
	light->minlight = 0;
	light->style = 0;
	light->stylevalue = 2.0f;
	light->falloff = 65536;

	light->Shadow = FALSE;
}


void R_SetupRTLight (dlight_t *dl)
{
	// figure which leaf the light is in
	mleaf_t *leaf = Mod_PointInLeaf (dl->origin, cl.worldmodel);

	// skip lights not in empty or water
	if (leaf->contents == CONTENTS_SOLID) return;
	if (leaf->contents == CONTENTS_SKY) return;

	// convert angles
	/*
	i'm assuming that this is meant to be a direction vector for spotlights but it would have been nice if LordHavoc had actually
	documented these things instead of just leaving us to figure it out from his code.  while we're at it - *another* disgusting
	text file format to be parsed?  why use an established format when you can always invent your own and inflict yet another
	custom text parser on the world?  or what's wrong with a binary format that you can just fread and have all your data ready
	to use immediately?  i sense some weenix loonyism at work here...
	*/
	dl->angles[0] = dl->angles[0] - 360 * floor (dl->angles[0] / 360);
	dl->angles[1] = dl->angles[1] - 360 * floor (dl->angles[1] / 360);
	dl->angles[2] = dl->angles[2] - 360 * floor (dl->angles[2] / 360);

	// default properties
	dl->Shadow = TRUE;

	// store out the radius to baseradius so that we can modulate radius by style instead of intensity
	dl->baseradius = dl->radius;

	// we have a valid light now
	r_num_realtimelights = (r_num_realtimelights + 1) % MAX_MAP_ENTITIES;
}


void R_LoadRTLightsFromRTLights (void)
{
	// build some RT lights for the map here
	char rtname[MAX_PATH];

	r_num_realtimelights = 0;

	strcpy (rtname, cl.worldmodel->name);
	FS_StripExtension (rtname, rtname);
	FS_DefaultExtension (rtname, ".rtlights");

	char *lightsstring = (char  *) FS_LoadFile (rtname, mapPool);

	if (lightsstring)
	{
		char *s = lightsstring;
		int n = 0;

		while (*s)
		{
			char *t = s;

			while (*s && *s != '\n' && *s != '\r') s++;
			if (!*s) break;

			char tempchar = *s;

			// take a light to work with
			dlight_t *dl = &r_realtimelights[r_num_realtimelights];

			// and set it's default properties
			R_DefaultRTLight (dl);

			// signifies that this light doesn't cast a shadow; just skip over it for now
			if (*t == '!')
			{
				dl->Shadow = FALSE;
				t++;
			}
			else dl->Shadow = TRUE;

			*s = 0;

			// lovely ugly text parsing; we've got a weenix loony at work here, people.
			// what's wrong with a binary file, just read it in, cast a pointer to it's data and we're done?
			// we don't use most of these params but they're here anyway for reading...
			char cubemapname[128];
			float corona, coronasizescale, specularscale;
			int flags;

			// yuck yuck yuck yuck, yuck yuck, fuck, BINARY!!!!  FOR GOD'S SAKE, BINARY!!!!
			int a = sscanf (t,
				"%f %f %f %f %f %f %f %d %127s %f %f %f %f %f %f %f %f %i",
				&dl->origin[0], &dl->origin[1], &dl->origin[2],
				&dl->radius,
				&dl->color[0], &dl->color[1], &dl->color[2],
				&dl->style,
				cubemapname,
				&corona,
				&dl->angles[0], &dl->angles[1], &dl->angles[2],
				&coronasizescale,
				&dl->ambientscale,
				&dl->diffusescale,
				&specularscale,
				&flags
			);

			*s = tempchar;

			if (a < 8)
			{
				// not enough properties for a basic light and we must assume that the .rtlights file is bad
				r_num_realtimelights = 0;
				return;
			}

			// setup the light
			R_SetupRTLight (dl);

			// line endings!  what kind of blithering idiot loves line endings anyway?
			if (*s == '\r') s++;
			if (*s == '\n') s++;
			n++;
		}
	}
}


struct lightepair_t
{
	lightepair_t	*next;
	char	key[MAX_KEY];
	char	value[MAX_VALUE];
};

struct lightentity_t
{
	char	classname[64];
	vec3_t	origin;
	float	angle;
	int		light;
	int		style;
	char	target[32];
	char	targetname[32];
	lightepair_t	*epairs;
	lightentity_t	*targetent;
};


void MatchTargets (lightentity_t *entities, int num_entities)
{
	// the only processing we do here is to update the target ent so that we can have spotlights
	for (int i = 0; i < num_entities; i++)
	{
		if (!entities[i].target[0])
			continue;

		for (int j = 0; j < num_entities; j++)
		{
			if (!strcmp (entities[j].targetname, entities[i].target))
			{
				entities[i].targetent = &entities[j];
				break;
			}
		}
	}
}


void R_LoadRTLightsFromBSPEntities (void)
{
	// most of this is copy-and-paste from light.exe but we don't do a certain amount of the processing
	// that does as we assume that the map has already been lit, and therefore doesn't need it.
	r_num_realtimelights = 0;

	int mark = qScratch->GetLowMark ();
	lightentity_t *entities = (lightentity_t *) qScratch->Alloc (MAX_MAP_ENTITIES * sizeof (lightentity_t));

	char *data = cl.worldmodel->entities;
	int num_entities = 0;

	// begin parsing
	while (1)
	{
		// parse the opening brace
		if (!(data = COM_Parse (data))) break;

		// it's expected that these will never fail, otherwise how could the BSP exist in the first place?
		if (com_token[0] != '{') Sys_Error ("LoadEntities: found %s when expecting {", com_token);
		if (num_entities == MAX_MAP_ENTITIES) Sys_Error ("LoadEntities: MAX_MAP_ENTITIES");

		// take this entity
		lightentity_t *entity = &entities[num_entities];
		num_entities++;

		// go through all the keys in this entity
		while (1)
		{
			char key[64];

			// parse key
			if (!(data = COM_Parse (data))) Sys_Error ("LoadEntities: EOF without closing brace");
			if (!strcmp (com_token, "}")) break;

			strcpy (key, com_token);

			// parse value
			if (!(data = COM_Parse (data))) Sys_Error ("LoadEntities: EOF without closing brace");

			int c = com_token[0];

			if (c == '}') Sys_Error ("LoadEntities: closing brace without data");

			// alloc an epair
			lightepair_t *epair = (lightepair_t *) qScratch->Alloc (sizeof (lightepair_t));

			// and copy it over
			strcpy (epair->key, key);
			strcpy (epair->value, com_token);
			epair->next = entity->epairs;
			entity->epairs = epair;

			// now let's see what we've got
			if (!strcmp (key, "classname"))
				strcpy (entity->classname, com_token);
			else if (!strcmp (key, "target"))
				strcpy (entity->target, com_token);
			else if (!strcmp (key, "targetname"))
				strcpy (entity->targetname, com_token);
			else if (!strcmp (key, "origin"))
			{
				double vec[3] = {0, 0, 0};

				// scan into doubles, then assign
				// which makes it vec_t size independent
				if (sscanf (com_token, "%lf %lf %lf", &vec[0], &vec[1], &vec[2]) != 3)
					Sys_Error ("LoadEntities: not 3 values for origin");

				for (int i = 0 ; i < 3 ; i++)
					entity->origin[i] = vec[i];
			}
			else if (!strncmp (key, "light", 5) || !strcmp (key, "_light"))
			{
				entity->light = atof (com_token);
			}
			else if (!strcmp (key, "style"))
			{
				entity->style = atof (com_token);

				if ((unsigned) entity->style > 254)
					Sys_Error ("Bad light style %i (must be 0-254)", entity->style);
			}
			else if (!strcmp (key, "angle"))
			{
				entity->angle = atof (com_token);
			}
		}

		// all fields have been parsed
		if (!strncmp (entity->classname, "light", 5) && !entity->light)
			entity->light = DEFAULTLIGHTLEVEL;

		// we're skipping over the LightStyleForTargetName stuff as it's assumed that the map has already been through light.exe
	}

	// match up targets for spotlights
	MatchTargets (entities, num_entities);

	// now we go through the entities again and generate RT lights from them
	for (int i = 0; i < num_entities; i++)
	{
		lightentity_t *entity = &entities[i];

		if (entity->light > 0)
		{
			// this is an RT light so add it to the list
			dlight_t *dl = &r_realtimelights[r_num_realtimelights];

			R_DefaultRTLight (dl);

			Vector3Copy (dl->origin, entity->origin);
			dl->radius = entity->light;
			dl->style = entity->style;

			// check contents
			mleaf_t *l = Mod_PointInLeaf (dl->origin, cl.worldmodel);

			// hack in some fake colour
			if (!strnicmp (entity->classname, "light_torch", 11))
			{
				if (!dl->style) dl->style = 1;
				Vector3Set (dl->color, DL_COLOR_ORANGE);
			}
			else if (!strnicmp (entity->classname, "light_flame", 11))
			{
				if (!dl->style) dl->style = 1;
				Vector3Set (dl->color, DL_COLOR_ORANGE);
			}
			else if (l->contents == CONTENTS_LAVA)
				Vector3Set (dl->color, DL_COLOR_RED);
			else if (l->contents == CONTENTS_SLIME)
				Vector3Set (dl->color, DL_COLOR_GREEN);

			// normalize the colour
			Vector3Normalize (dl->color);
			dl->diffusescale = 2.0f;

			// set up the light - this is done before the spotlight check so that we don't mess up the spotvector
			// (which assumes that my guess about "angles" in .rtlights is correct, and that we decide to reuse them for this)
			R_SetupRTLight (dl);

			// check for a spotlight
			if (entity->targetent)
			{
				Vector3Subtract (dl->spotvec, entity->targetent->origin, entity->origin);
				Vector3Normalize (dl->spotvec);

				if (!entity->angle)
					dl->falloff = -cos (20 * M_PI / 180);
				else dl->falloff = -cos (entity->angle / 2 * M_PI / 180);
			}
			else dl->falloff = 65536;
		}
	}

	qScratch->FreeToLowMark (mark);
}


void R_LoadRealtimeLight (void)
{
	// reload all lighting on map change
	r_num_realtimelights = 0;

	R_LoadRTLightsFromRTLights ();

	if (!r_num_realtimelights)
		R_LoadRTLightsFromBSPEntities ();

	// reset visibility cos we mucked with it while loading
	R_VisReset ();
}


void R_ReloadRTLights (void)
{
	if (cls.state != ca_connected)
	{
		Con_Printf (PRINT_SAFE, "R_ReloadRTLights : cannot reload while disconnected\n");
		return;
	}

	R_LoadRealtimeLight ();
	Con_Printf (PRINT_SAFE, "Loaded %i rtlights\n", r_num_realtimelights);
}

